iT邦幫忙

2023 iThome 鐵人賽

DAY 21
0
Modern Web

給前端新手的圖文故事系列 第 21

前進 React 之前的 JavaScript ES6

  • 分享至 

  • xImage
  •  

ES6(ES2015) 是目前最新的官方版本,因其標準在 2015 年中才定案,因此該時間前開發的瀏覽器版本會出現部分不支援的問題,而過往普遍所使用的 JavaScript 版本 ES5(ECMAScript 5) 則是在 2009 年底發行的,這時間跨度其實相當的大,而當中的 ECMAS 是指歐洲計算機製造協會(European Computer Manufactures Association),其中設立的 TC39 負責了該標準的討論與走向.

其實上方的名稱定義與歷史定位等,並不會是我們需要認真探討的點,我們需要知道的是因為發布時間的關係,導致像 IE11 等瀏覽器可能會出現對 ES6 語法不支援的問題,而這也是前端工程師所面對的世紀難題.

從宣告開始學習 ES6 語法

在之前的課程中我們有稍微提過變數宣告的部分,在 ES6 中新增了兩個夥伴,以下就讓我們從宣告再次開始吧!

// 這是變數宣告有可能看到的方式,但除了最後一個外基本上都是在 window 上建立全域變數
a = 10
this.a = 10
window.a = 10
var a = 10

需要先介紹上方操作的原因是,JavaScript 是一套非常自由的語言,以上均可使程式正常運行,並達成相同的結果,也就是說 var 本身其實在某方面而言是非必要的.
會有上方的操作的原因,是因為 var 是所謂的函示作用域(function scope),而現在要開始介紹的 let 與 const 則不同,他們屬於區塊作用域(Block scope),雖然說起來還蠻抽象的,但其實我們實際看 code 的時候,結果是非常一目瞭然的.

// 使用 var 進行的宣告會以 function 做為分隔區塊
function test(){
    var a = 10
}
if(true){
    var b = 20
}

console.log(a) // a is not defined
console.log(b) // 20
// 使用 let 或 const 進行的宣告會以 「區塊」 做為分隔區塊
function test(){
    let a = 10
}
if(true){
    const b = 20
}

console.log(a) // a is not defined
console.log(b) // b is not defined

在上方的範例中,大家應該會發現第二種的行為操作更加合理,因為實際上 function scope 很容易造成部分變數作用的混淆,並且在觀看上也相對不直覺,且大多的撰寫操作中,也會盡量在程式開頭就先完成 var 的操作,雖然理論上是在建立全域變數,但追根究底其實就是在 window object 上建立變數存取而已.

下方範例就是很明顯的操作,使用 var 的 for 將會在 window object 建立 index 這個變數,這很有可能會跟其他的變數互相影響從而導致錯誤

for (var index = 0; index < 3; index++) {}
console.log(index); // 取得 3

for (let index = 0; i < 6; i++) {}
console.log(i); // i is not defined

那介紹完了作用域,接下來就讓我們從兩個宣告所使用的方式與地方著手吧!

const

const 其實就是常數的意思,真正的含義其實就是固定的值,以下方的案例為例,這樣操作就會導致程式出錯.

const a = 1
a = 2 // 錯誤訊息

以上的操作有一個核心重點,是常數本身不可被變更,但在 JavsScript 中是存在 「傳址」 這個概念的,因此如果將常數定義為物件型別(array、object等)而非純值的話,他還是可以進行修改的,只是若給予他一個全新的 地址 的話,他還是會發生錯誤

const a = {};
a.foo = 2;
console.log(a);
a = { b: 1 }; // 錯誤訊息

let

let 其實就沒有那麼多額外的規則了,他與 const 的差別只有他作為一個變數,可以任意的修改與刪除,現在慣用的操作方式均拿他來取代 var 進行變數定義,因為再如 for 的迴圈中,使用 var 很容易造成相互影響的錯誤

提升(Hoistion)

在之前的範例中,我們可能習慣了使用為賦值的變數將出現 undefined 的標語,但在使用 let 與 const 時這會直接錯誤,但原因其實並非他們沒有進行提升,而是因為一個名為時間死區的機制(Temporal Dead Zone),簡單來說就是在賦值成功並進入到作用域之前,為了防止有任進行操作而設置的保護機制

let x = 'outer scope';

(function(){
    console.log(x)
    // let x = 'inner scope' // 這行將導致錯誤
})()

設計規範:以目前最流行的設計指南而言,我們會將物件類型以及常數使用 const 進行宣告,而變數類型使用 let 宣告,而 var 的部分則幾乎不會使用

箭頭函示

在 ES6 中新增了一個函式的撰寫方式,也就是這邊要探討的 Arrow Function,要注意的是他並不是過去 function 的縮寫語法,而是一個經過改良並重新設計的新語法,最主要的功能是在沒有 {} 包覆功能時,將會自動 return 右方的內容(這在 React 中很常用),且不在需要 function 關鍵字

// ES5
var x = function(x, y) {
   return x * y;
}

// ES6
const x = (x, y) => x * y;

需要注意的地方

  • Arrow Function 並不會被提升,因此需要在使用前宣告
  • 建議使用 const 進行宣告
  • 若函式使用 {} 進行包覆,則不會自動 return 任何東西

基本函示定義語法(概念補充)

其實在函示定義中,存在著所謂的匿名函示,通常用在指定給一個識別名或是單次的操作功能

// 匿名函示
function(){}

// 有名字的函示
function foo(){}

(function(){
    console.log('匿名函示範例')
})()

函示預設值

在 ES5 的標準中,我們定義函示的預設值其實相對不方便,我們需要藉由額外的判斷來處理,但在 ES6 中,我們有了更簡便的方式

// 過去寫法
function myFunction(x, y) {
    var x = x || 10
    var y = y || 10
    return x + y;
}
myFunction(5); 

function myFunction(x=10, y = 10) {
  return x + y;
}
myFunction(5); 

展開運算式

展開運算值是 ES6 中非常好用的一個操作,他可以藉由將其他 array 展開在內容的方式,去複製其他陣列的內容,原理上跟各自進行 push 一樣,且會是完全新的資料,不會觸發淺複製的問題

const arrayA = [1,2,3]
const arrayB = [1,2,3,...arrayA]
console.log(arrayB) //[1, 2, 3, 1, 2, 3]

在函示參數裡協助展開

const sum=(a,b,c)=> a + b + c
const arrayA = [1,2,3]
console.log(sum(...arrayA)) // 6

也可以將字串展開

const arrayA = [1,2,3]
console.log(...arrayA) // 1 2 3

注意:展開運算式雖然會與原本的資料做區隔,但若是展開的資料內也有物件型態的索引,則該內容將一樣共享過往的參照。
如若想要完全複製,可以考慮使用 es5 的 JSON.stringify() 先將物件或陣列轉換成 JSON 格式,接著再由 JSON.parse() 轉換回來。

解構賦值

解構賦值其實正確來說應該是解構並且指定值,只是在網路上中文都流傳為解構賦值,他是拿來提取固定指定值的一個操作,在 React 中會非常平凡的出現,這邊就先讓我們看一下基本的範例吧!

陣列操作

// 基本操作
const [a,b]=[1,2] // a=1 b=2

// 若需先建立變數
let a,b;
const [a,b]=[1,2] // a=1 b=2

// 可以跳過某值
const [a,,b]=[1,2,3] // a=1 b=2

// 其餘運算式
const [a,...b]=[1,2,3] // a=1 b=[2,3]

基本上可以理解為鏡像對照,且可以進行多維或複雜的操作

物件操作

// 基本操作
const {user:x}={user:5} // x=5

// 失敗操作
const {user2:x}={user:5} // x=undefined 因為前綴名稱不同

// 賦予新的變數名稱
const {prop:x,prop2:y} = {prop:10,prop2:20} // x=10 y =20

// 屬性賦值寫法
const {prop:prop,prop2:prop2}={prop:5,prop:10} // prop=5 prop2=10

// 簡短語法
const {prop,prop2}={prop:5,prop2:10} // prop=5 prop2=10

// 若需使用 let 先宣告
let a,b
({a,b}={a:1,b:2}) // a=1,b=2

// 需要額外操作的原因是{}雖然是物件宣告的方法,但同時也是 function 的區塊,因此這邊需要額外設計

簡單的範例

// 假設這些資料來至於外部 api,而我們需要其中的一部分內容進行顯示
const turtle = {
	name: '鑽石 🐢',
	legs: 4,
	shell: true,
	type: '北美鑽紋龜',
	meal: 10,
	diet: '生魚片?'
}

function feed(animal) {
	return `餵食 ${animal.name} ${animal.meal} 小片 ${animal.diet}`;
}

// 更良好的解答

// function feed({ name, meal, diet }) {
// 	return `餵食 ${name} ${meal} 小片 ${diet}`;
// }

// 或者

// function feed(animal) {
// 	const { name, meal, diet } = animal;
// 	return `餵食 ${name} ${meal} 小片 ${diet}`;
// }

console.log(feed(turtle))

<常用迴圈> Map 固定次數的迴圈操作 會建立一個新個陣列,並執行提供的操作

  var array = ['apple', 'banana', 'orange'];
  
  array.map(function (element){
  	document.write(element);
  })

  console.log(array);

  const array1 = [1, 4, 9, 16];

  const map1 = array1.map(x => x * 2);
  
  const map2 = array1.map(function (x) {
    return x * 2;
  });

  console.log(array1);
  console.log(map1);
  console.log(map2);

<常用迴圈> For/of 對目標對象逐一進行操作

  • For/of 語法可以對目標對象進行迭代操作
  • For/of 可以提供 Arrays(陣列), Strings(字串), Maps, NodeLists(節點列表)跟其他大部分的類似結構

操作字串

let courseName = 'JavaScript課程';
let text = '';

for (let word of courseName) {
  text += word + ' ';
}

console.log(text);
// 輸出結果:J a v a S c r i p t 課 程 

操作陣列

let courses = ['JavaScript課程', 'FrontEnd課程', 'CSS特效課程', 'UI/U課程'];

for (let course of courses) {
  console.log(course);
}

// 輸出結果:
// JavaScript課程
// FrontEnd課程
// CSS特效課程
// UI/U課程

<物件操作> Maps 建立一個可供方便對照的 map 物件,並且具有 key 值不會重複等優點

Map 主要是拿來建立一些作為參考的物件,如下面的水果價目表等,他的 key 具有不可重複的特性,因此如果使用 set 函式重複操作到的話,他將會將原本的 value 直接進行取代

// 使用 new Map() 函式建立一個 Map 物件,並提供 key 與value
const fruits = new Map([
  ['apples', 500],
  ['bananas', 300],
  ['oranges', 200],
]);

// 取得 Map 物件內 key 的 value
console.log(fruits.get('apples')); // 顯示: 500

// 確認目前 size 的大小
console.log(fruits.size); // 顯示: 3

// 確認 Map 物件有沒有對應的 key
console.log(fruits.has('bananas')); // 顯示:true

// 顯示 Map 物件內的 keys
console.log(fruits.keys()); // 顯示:MapIterator {'bananas', 'oranges', 'pineapples'}

// 顯示 Map 物件內的 values
console.log(fruits.values()); // 顯示:MapIterator {500, 300, 200}

// 對 Map 物件進行設置
fruits.set('pineapples', 400);
console.log(fruits); // 顯示:Map(4) {'apples' => 500, 'bananas' => 300, 'oranges' => 200, 'pineapple' => 400}

// 對 Map 物件進行移除
fruits.delete('apples');
console.log(fruits); // 顯示:Map(3) {'bananas' => 300, 'oranges' => 200, 'pineapple' => 400}

// 對 Map 物件進行清空
fruits.clear();
console.log(fruits); // 顯示:Map(0) {size: 0}

<陣列操作> Sets

Set 陣列的內容是不可以重複的,因此可以讓我們進行重複值得過濾與參考

// 使用 new Set() 函式建立一個 Set 物件,並提供 value
const fruits = new Set(['apples', 'bananas', 'oranges']);

// 確認目前 size 的大小
console.log(fruits.size); //顯示: 3

// 確認 Set 物件有沒有對應的 key
console.log(fruits.has('bananas')); // 顯示:true

// 顯示 Set 物件內的 value,這裡因為 Set 物件沒有 key,所以會與 Map 類同
console.log(fruits.keys()); // 顯示:SetIterator {'apples', 'bananas', 'oranges'}

// 顯示 Set 物件內的 values
console.log(fruits.values()); // 顯示:SetIterator {'apples', 'bananas', 'oranges'}

// 對 Set 物件進行新增
fruits.add('pineapples');
console.log(fruits); // 顯示:Set(4) {'apples', 'bananas', 'oranges', 'pineapples'}

// 對 Set 物件進行刪除
fruits.delete('apples');
console.log(fruits); // 顯示:Set(3) {'bananas', 'oranges', 'pineapples'}

// 對 Set 物件進行清除
fruits.clear();
console.log(fruits); // 顯示:Set(0) {size: 0}

簡單的範例

// 假設這些資料來至於外部 api,而我們需要其中的一部分內容進行顯示
var randomNumber = function (pointNumber, maxNumber) {
  let text = '產出亂碼 - ';
  let numberArray = new Set();

  // 印出的總數,不可大於最大數字的範圍
  if (pointNumber > maxNumber) return '請輸入最大範圍要大於總筆數';

  // 做一個 while 迴圈,判斷 numberArray 的 size 小於 pointNumber 的的,條件成立並運行裡面的程式
  while (numberArray.size < pointNumber) {
    // 幫助個 Set 物件新增一個隨機的範圍內亂數
    numberArray.add(Math.ceil(Math.random() * maxNumber));
  }

  // 幫 text 這個變數,加上 numberArray 分隔插入、的字串,但因為 Set 物件無法使用 join 的操作,因此先將它轉換成 Array
  text += Array.from(numberArray).join('、');

  return text;
};

console.log(randomNumber(6, 20));

React 當中我們可能會遇到的 class 概念

從令人困惑的 this 開始學習

this 是 JavaScript 當中一個非常容易讓人困惑的關鍵字,他是一個特殊的識別關鍵字(identifier keyword),會自動定自在每個函式的範疇(scope)內,但實際上要搞清楚他究竟參考了(refers)什麼,是個連資深的開發者都會感到頭疼的問題。

因為我們僅是要稍微了解 ES6 中 class 對 this 的操作,因此在這邊並不會對 this 做太多筆墨的琢磨,各位同學可以有個最基本的認知,就是 this 所綁定的地方,為該觸發環境中的堆疊階層,簡單來說就是在電腦執行緒中被呼叫的位子,我們將使用以下的範例進行簡單的介紹

console.log(this); 
// 會顯示 window 是因為他在 window 上觸發

function CallMe(name) {
  this.name = name;
  console.log(this);
}

// 使用 function 去動態建立一個物件
var me = new CallMe('Iffy');

// 建立時 會觸發 console.log(this) 的功能,並列印該物件本身(因為是該物件觸發)

在上方的範例中,我們使用了一個 new 的方法,去進行動態物件的建立,會需要這樣做的原因,是因為在物件導向的語言當中,我們往往會去繼承與依賴提他的物件,而在 JavaScript 中的物件導向也是如此,但他的傳統做法實在上很常令人感到困惑。

function CallMe(name) {
  this.name = name;
}
CallMe.prototype.print = function(){
  this.name = name;
}
// 若需要 function 綁定需額外撰寫 prototype
var me = new CallMe('Iffy');

me.print();

這邊有一個特點需要注意,當我們在建立這種可以重複利用的「類別」時,將採用大駝峰命名

嚴格模式

JavaScript 在設計上特別設置了一個嚴格模式,他使得 this 在沒有觸發堆疊時,不會自動進行 window 的綁定,但要注意的是該設置基本上應該會影響怎麼運行,需注意在 引入 JavaScript 其他的函示庫後的使用。

'use strict'; // 定義 JavaScript 為 嚴格模式,使 this 不會自動綁定 window

(function () {
  console.log(this);
})();

在上方的範例中我們不難看出,JavaScript 在進行物件導向的設計時,會因為各種細節處理而導致使用起來不如預期,究其原因,是 JavaScript 在 ES6 之前並沒有所謂 類(class) 的設計,這使得我們儘管在操作行為與實作上接近物件導向,但實際在撰寫上卻沒有那麼方便,而這裡就讓我們從範例看所謂的 class 是什麼吧!

// 建立類別
class CallMe {
  constructor(name) {
    this.name = name;
  }
  print() {
    console.log(this);
  }
}

// 建立物件實體
const iffyCall = new CallMe('Iffy');

// 顯示物件內容(會發現與之前一樣在 prototype 中有建立 point function)
console.log(iffyCall);

// 操作物件功能
iffyCall.print();

其實 class 的概念就像是藍圖,各位可以把它想像成零件的設計圖,而 new 則是建立這個藍圖為實體的方法,而藍圖的重點基本可以往重複利用以及方向閱讀的方面設想,也就是組件化元件的概念,同時也可以讓多張藍圖堆疊組成如以下範例。

class Book {
  constructor(title) {
    this.title = title;
  }
  getTitle() {
    return this.title;
  }
}

class EBook extends Book {
  constructor(title, link) {
    super(title);  // 用來呼叫執行父母類別的建構式(constructor)
    this.link = link;
  }
  getLink() {
    return this.link;
  }
}

const myBook = new EBook('書本名稱', '書本連結');
console.log(myBook);
console.log(myBook.getTitle());

上一篇
暸解 SPA 基礎概念與介紹現今的前端架構
下一篇
React 基礎概念學習與認知
系列文
給前端新手的圖文故事30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言